package com.pkk.cloud.support.oauth2.client.detail.config;

import com.pkk.cloud.support.oauth2.client.detail.client.entity.PenguinOauthClient;
import com.pkk.cloud.support.oauth2.client.detail.client.entity.PenguinOauthClientScope;
import com.pkk.cloud.support.oauth2.client.detail.client.service.PenguinOauthClientScopeService;
import com.pkk.cloud.support.oauth2.client.detail.client.service.PenguinOauthClientService;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.NoSuchClientException;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.stereotype.Service;

/**
 * @description: 基于数据库和缓存实现的客户端的详细信息
 * <p>
 * 获取客户端的详细信息
 * <p>
 * 并且在访问接口的时候,会在获取当前用户信息接口:【/current】之后进行的客户端scope的范围认证
 * @author: peikunkun
 * @create: 2020-03-17 17:22
 **/
@Slf4j
@Service
public class DatabaseCachableClientDetailsServiceImpl implements ClientDetailsService {


  @Resource
  private PenguinOauthClientService penguinOauthClientService;
  @Resource
  private PenguinOauthClientScopeService penguinOauthClientScopeService;


  /**
   * @param clientId The client id.
   * @return The client details (never null).
   * @throws ClientRegistrationException If the client account is locked, expired, disabled, or invalid for any other
   * reason.
   * @Desc: 根据客户端id获取客户端的详细信息 Load a client by the client id. This method must not return null.
   * <p>
   * 源码参见【DefaultTokenServices#loadAuthentication】
   */
  @Override
  public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
    final Optional<PenguinOauthClient> oauthClientByClientId = Optional
        .of(penguinOauthClientService.getOauthClientByClientId(clientId));
    return oauthClientByClientId.map(entityDomain)
        .orElseThrow(() -> new NoSuchClientException("Client ID not found ->[" + entityDomain + "]"));
  }


  /**
   * @Description: 对方法#loadClientByClientId的oauthClientByClientId进行额外解析操作
   * @return:
   * @Author: peikunkun
   * @Date: 2020/3/18 0018 上午 9:17
   */
  private final Function<? super PenguinOauthClient, ? extends BaseClientDetails> entityDomain = entity -> {
    //基础的客户端信息
    BaseClientDetails clientDetails = new BaseClientDetails();
    clientDetails.setClientId(entity.getClientId());
    clientDetails.setClientSecret(entity.getClientSecret());
    clientDetails.setAccessTokenValiditySeconds(entity.getAccessTokenValiditySeconds());
    clientDetails.setRefreshTokenValiditySeconds(entity.getRefreshTokenValiditySeconds());
    //授权类型如【refresh_token(刷新token),password(密码),client_credentials(客户端凭证)】
    clientDetails.setAuthorizedGrantTypes(Arrays.asList(StringUtils.split(entity.getGrantType(), ',')));

    //获取授权范围
    List<PenguinOauthClientScope> scopes = this.penguinOauthClientScopeService
        .findClientScopeByClientId(entity.getClientId());
    //设置要授权访问的后台客户端信息[如module-1,module-2]
    //指定客户端申请的权限范围,可选值包括read,write,trust;其实授权赋予第三方用户可以在资源服务器获取资源，第三方访问资源的一个权限，访问范围。
    clientDetails.setScope(scopes.stream().map(scope -> scope.getScope()).collect(Collectors.toList()));

    //设置是否自动审查
    clientDetails.setAutoApproveScopes(
        scopes.stream().filter(PenguinOauthClientScope::getAutoApprove).map(scope -> scope.getScope())
            .collect(Collectors.toList()));

    //设置资源id信息
    if (StringUtils.isNotBlank(entity.getResourceIds())) {
      clientDetails
          .setResourceIds(Arrays.stream(StringUtils.split(entity.getResourceIds(), ',')).collect(Collectors.toList()));
    }
    //客户端的重定向URI
    if (StringUtils.isNotBlank(entity.getRedirectUri())) {
      clientDetails.setRegisteredRedirectUri(
          Arrays.stream(StringUtils.split(entity.getRedirectUri(), ',')).collect(Collectors.toSet()));
    }
    //additionalInformation：这是一个预留的字段,在Oauth的流程中没有实际的使用,可选,但若设置值,必须是JSON格式的数据
    clientDetails.setAdditionalInformation(Collections.emptyMap());
    return clientDetails;
  };
}
